using System;
using System.Collections.Generic;
using System.Text;

using System.Drawing;
using System.Drawing.Imaging;

namespace PictureMimic
{
	/*** Base Fitness Model Class ***/
	public abstract class CandidateFitnessModel
	{
		public abstract ulong CalculateFitness(CandidateDNA candidate, Bitmap target);

		protected virtual ulong ScorePixel(Color candidate, Color target)
		{
			int r = candidate.R - target.R;
			int g = candidate.G - target.G;
			int b = candidate.B - target.B;

			return (ulong)((r * r) + (g * g) + (b * b));
		}

		protected virtual ulong CalculateFitness(Bitmap proposed, Bitmap target, int width, int height)
		{
			ulong fitness = 0;

			BitmapData proposedData = proposed.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
			BitmapData targetData = target.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					fitness += ScorePixel(GetPixel(proposedData, x, y), GetPixel(targetData, x, y));
				}
			}

			proposed.UnlockBits(proposedData);
			target.UnlockBits(targetData);

			return fitness;
		}

		protected virtual unsafe Color GetPixel(BitmapData data, int x, int y)
		{
			byte* pixel = (byte*)data.Scan0 + y * data.Stride + 3 * x;
			return Color.FromArgb(pixel[2], pixel[1], pixel[0]);
		}
	}

	/*** Fill Pixel Delta Check ***/
	public class PixelDeltaFitness : CandidateFitnessModel
	{
		public override ulong CalculateFitness(CandidateDNA candidate, Bitmap target)
		{
			Bitmap proposed = candidate.GetBitmap();

			return CalculateFitness(proposed, target, EvolutionaryConstraints.EnvironmentWidth, EvolutionaryConstraints.EnvironmentHeight);
		}
	}

	/*** X-Stepping Quick Pixel Delta Scan***/
	public class PixelDeltaQSXFitness : CandidateFitnessModel
	{
		public override ulong CalculateFitness(CandidateDNA candidate, Bitmap target)
		{
			ulong fitness = 0;

			Bitmap proposed = candidate.GetBitmap();

			BitmapData proposedData = proposed.LockBits(new Rectangle(0, 0, EvolutionaryConstraints.EnvironmentWidth, EvolutionaryConstraints.EnvironmentHeight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
			BitmapData targetData = target.LockBits(new Rectangle(0, 0, EvolutionaryConstraints.EnvironmentWidth, EvolutionaryConstraints.EnvironmentHeight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

			int x = 0;

			while (x < EvolutionaryConstraints.EnvironmentWidth)
			{
				for (int y = 0; y < EvolutionaryConstraints.EnvironmentHeight; y++)
				{
					fitness += ScorePixel(GetPixel(proposedData, x, y), GetPixel(targetData, x, y));
				}
				x += 5;
			}

			proposed.UnlockBits(proposedData);
			target.UnlockBits(targetData);

			return fitness;
		}
	}

	/*** Y-Stepping Quick Scan ***/
	public class PixelDeltaQSYFitness : CandidateFitnessModel
	{
		public override ulong CalculateFitness(CandidateDNA candidate, Bitmap target)
		{
			ulong fitness = 0;

			Bitmap proposed = candidate.GetBitmap();

			BitmapData proposedData = proposed.LockBits(new Rectangle(0, 0, EvolutionaryConstraints.EnvironmentWidth, EvolutionaryConstraints.EnvironmentHeight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
			BitmapData targetData = target.LockBits(new Rectangle(0, 0, EvolutionaryConstraints.EnvironmentWidth, EvolutionaryConstraints.EnvironmentHeight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

			int y = 0;

			while (y < EvolutionaryConstraints.EnvironmentHeight)
			{
				for (int x = 0; x < EvolutionaryConstraints.EnvironmentWidth; x++)
				{
					fitness += ScorePixel(GetPixel(proposedData, x, y), GetPixel(targetData, x, y));
				}
				y += 5;
			}

			proposed.UnlockBits(proposedData);
			target.UnlockBits(targetData);

			return fitness;
		}
	}

	/*** Alternating Quick Scan ***/
	public class PixelDeltaQSAFitness : CandidateFitnessModel
	{
		private bool ScanY = false;

		public override ulong CalculateFitness(CandidateDNA candidate, Bitmap target)
		{
			CandidateFitnessModel model;

			if (ScanY = !ScanY)
			{
				model = new PixelDeltaQSYFitness();
			}
			else
			{
				model = new PixelDeltaQSXFitness();
			}

			return model.CalculateFitness(candidate, target);
		}
	}

	/*** Compressed Delta ***/
	public class CompressedDeltaFitness : CandidateFitnessModel
	{
		public override ulong CalculateFitness(CandidateDNA candidate, Bitmap target)
		{
			System.Drawing.Image.GetThumbnailImageAbort callback = new Image.GetThumbnailImageAbort(ThumbnailCallBack);

			Bitmap proposed = candidate.GetBitmap();

			Bitmap compressedProposed = new Bitmap(proposed.GetThumbnailImage(100, 100, callback, System.IntPtr.Zero));
			Bitmap compressedTarget = new Bitmap(target.GetThumbnailImage(100, 100, callback, System.IntPtr.Zero));

			return CalculateFitness(compressedProposed, compressedTarget, 100, 100);
		}

		private bool ThumbnailCallBack()
		{
			// Bogus callback, because GDI needs it even though it doesn't use it!
			return true;
		}
	}
}
